﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using System.Web;
using System.Collections.Specialized;

public class PassportConnection
{
	private Uri _Uri;
	private CookieContainer _Cookies;
	private string _UserName;
	private string _Password;

	private string _PassportParams;
	private string _PassportLocation;

	public string UserAgent { get; set; }

	public PassportConnection(string userName, string password)
	{
		_UserName = userName;
		_Password = password;
		_Cookies = new CookieContainer();
		_PassportParams = null;
		_PassportLocation = null;
		UserAgent = "Microsoft-WebDAV-MiniRedir/6.1.7600";
	}

	public HttpWebResponse SendRequest(string url)
	{
		return SendRequest(new InternalWebRequest("GET", url));
	}

	public HttpWebResponse SendRequest(string url, string verb)
	{
		return SendRequest(new InternalWebRequest(verb, url));
	}

	public HttpWebResponse SendSoapRequest(string url, string soapAction, string soapEnvelope)
	{
		InternalWebRequest req = new InternalWebRequest("POST", url);
		req.ContentType = "text/xml; charset=utf-8";
		req.Data = soapEnvelope;
		req.Headers["SOAPAction"] = soapAction;
		return SendRequest(req);
	}

	public static string GetResponseData(HttpWebResponse resp)
	{
		StreamReader reader = new StreamReader(resp.GetResponseStream(), true);
		string data = reader.ReadToEnd();
		reader.Close();
		return data;
	}

	protected HttpWebRequest CreateRequest(InternalWebRequest spec)
	{
		HttpWebRequest req = (HttpWebRequest)WebRequest.Create(spec.Url);
		req.Method = spec.Verb;
		req.AllowAutoRedirect = false;
		req.PreAuthenticate = false;
		req.CookieContainer = _Cookies;
		req.KeepAlive = true;
		req.UserAgent = UserAgent;
		req.Headers.Add("translate", "f");
		foreach (string headerName in spec.Headers)
		{
			req.Headers.Add(headerName, spec.Headers[headerName]);
		}
		if (spec.ContentType != null)
			req.ContentType = spec.ContentType;
		if (spec.Data != null)
		{
			StreamWriter writer = new StreamWriter(req.GetRequestStream(), Encoding.UTF8);
			writer.Write(spec.Data);
			writer.Close();
		}
		return req;
	}

	protected HttpWebResponse SendRequest(InternalWebRequest reqSpec)
	{
		_Uri = new Uri(reqSpec.Url);
		try
		{
			HttpWebRequest req = CreateRequest(reqSpec);
			HttpWebResponse resp = SendRequest(req);
			if (CheckPassportAuthentication(resp))
			{
				string loginUrl = GetPassportLoginUrl();
				string token = GetPassportToken(loginUrl);

				// Retry the original url
				req = CreateRequest(reqSpec);
				string passportAuth = string.Format("Passport1.4 from-PP={0}", token);
				req.Headers.Add("Authorization", passportAuth);
				resp = (HttpWebResponse)req.GetResponse();
			}
			return resp;
		}
		catch (WebException err)
		{
			System.Diagnostics.Trace.WriteLine(string.Format("WebException: {0}", err.Message));
			StreamReader reader = new StreamReader(err.Response.GetResponseStream(), true);
			string content = reader.ReadToEnd();
			System.Diagnostics.Trace.WriteLine(content);
			reader.Close();
			return (HttpWebResponse)err.Response;
		}
	}

	private HttpWebResponse SendRequest(HttpWebRequest req)
	{
		return (HttpWebResponse)req.GetResponse();
	}

	private bool CheckPassportAuthentication(HttpWebResponse resp)
	{
		string authenticate = resp.Headers["WWW-Authenticate"];
		if (authenticate == null)
			return false;

		int find = authenticate.IndexOf(' ');
		if (find < 0)
			return false;

		_PassportParams = authenticate.Substring(find + 1);
		_PassportLocation = resp.Headers["Location"];
		return true;
	}

	private string GetPassportLoginUrl()
	{
		HttpWebResponse resp = SendRequest(CreateRequest(new InternalWebRequest("GET", "https://nexus.passport.com/rdr/pprdr.asp")));
		string header = resp.Headers["PassportURLs"];
		if (string.IsNullOrEmpty(header))
			throw new PassportException(PassportException.Reason.Protocol, "Expected header PassportURLs in GetPassportLoginUrl");
		Dictionary<string, string> passportUrls = ParseHeaderParams(header);
		string loginUrl = passportUrls["DALogin"];
		if (string.IsNullOrEmpty(loginUrl))
			throw new PassportException(PassportException.Reason.Protocol, "Expected parameter DALogin in header PassportURLs in GetPassportLoginUrl");
		if (!loginUrl.StartsWith("http"))
			loginUrl = "https://" + loginUrl;
		return loginUrl;
	}

	private string GetPassportToken(string loginUrl)
	{
		string authorize = string.Format("Passport1.4 sign-in={0},pwd={1},OrgVerb=OPTIONS,OrgUrl={2},{3}",
			HttpUtility.UrlEncode(_UserName), HttpUtility.UrlEncode(_Password), _Uri.ToString(), _PassportParams);

		InternalWebRequest spec = new InternalWebRequest("GET", loginUrl);
		spec.Headers["Authorization"] = authorize;
		HttpWebResponse resp;
		try
		{
			resp = SendRequest(CreateRequest(spec));
		}
		catch (WebException err)
		{
			resp = (HttpWebResponse)err.Response;
			if (resp.StatusCode == HttpStatusCode.Unauthorized)
				throw new PassportException(PassportException.Reason.Authentication, "Authentication did not succeed in GetPassportToken. Wrong user name or password.");
			else
				throw;
		}

		string header = resp.Headers["Authentication-Info"];
		if (string.IsNullOrEmpty(header))
			throw new PassportException(PassportException.Reason.Protocol, "Expected header Authentication-Info in GetPassportToken");
		Dictionary<string, string> authInfoParams = ParseHeaderParams(StringHelpers.After(header, ' '));
		string status = authInfoParams["da-status"];
		if (string.IsNullOrEmpty(status))
			throw new PassportException(PassportException.Reason.Protocol, "Expected parameter da-status in header Authentication-Info in GetPassportToken");
        if (status == "redir")
        {
            String redir = resp.Headers["Location"];
            string redirToken = GetPassportToken(redir);
            return redirToken;
        }
        if (status != "success")
			throw new PassportException(PassportException.Reason.Authentication, "Authentication did not succeed in GetPassportToken: " + status);
		string partnerToken = authInfoParams["from-PP"];
		if (string.IsNullOrEmpty(partnerToken))
			throw new PassportException(PassportException.Reason.Protocol, "Expected parameter from-PP in header Authentication-Info in GetPassportToken");
		return partnerToken;
	}

	private static Dictionary<string, string> ParseHeaderParams(string header)
	{
		Dictionary<string, string> dict = new Dictionary<string, string>();
		foreach (string part in header.Split(','))
		{
			int find = part.IndexOf('=');
			if (find >= 0)
			{
				dict[HttpUtility.UrlDecode(part.Substring(0, find).Trim())] = HttpUtility.UrlDecode(part.Substring(find + 1).Trim());
			}
		}
		return dict;
	}

	protected class InternalWebRequest
	{
		public string Url;
		public string Verb;
		public string Data;
		public string ContentType;
		public NameValueCollection Headers;

		public InternalWebRequest(string verb, string url)
		{
			Url = url;
			Verb = verb;
			Data = null;
			ContentType = null;
			Headers = new NameValueCollection();
		}
	}
}
